#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>

#include "xmalloc.h"
#include "xmalloc_dbg.h"

static unsigned long total_bytes_allocd = 0;
static unsigned long total_bytes_freed = 0;
static unsigned long total_blocks_allocd = 0;
static unsigned long total_blocks_freed = 0;
#ifdef ENABLE_PTHREADS
static pthread_mutex_t thrd_alloc = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t thrd_realloc = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t thrd_free = PTHREAD_MUTEX_INITIALIZER;
#endif
#ifdef ENABLE_REQDBG
__NCTLS size_t total_request_bytes = 0;
#endif

#ifdef ENABLE_REQDBG
void
xmemstat_print(int thrdnum, size_t reqnum)
{
  D("[%d][%ld] [Memory Usage] %ld bytes used for this request", thrdnum, reqnum, total_request_bytes);
  total_request_bytes = 0;
}
#endif

void *
xmalloc(size_t n, size_t size)
{
  void *ptr;

  ptr = (void *) calloc(n, size);
  assert(ptr);
#ifdef ENABLE_PTHREADS
  pthread_mutex_lock(&thrd_alloc);
#endif
  total_bytes_allocd += n*size;
  total_blocks_allocd++;
  xmem_obj_add(ptr, n*size);
#ifdef ENABLE_PTHREADS
  pthread_mutex_unlock(&thrd_alloc);
#endif
#ifdef ENABLE_REQDBG
  total_request_bytes += n*size;
#endif
  return (ptr);
}

#if defined(PRINT_MEM) || defined(ENABLE_DEBUG)
void *
xmalloc_dbg(size_t n, size_t size, const char *file, const uint16_t line)
{
  void *ptr = xmalloc(n, size);
  xmem_obj_setfl(ptr, n*size, file, line);
#ifdef PRINT_MEM
  log_to(file, line, log_debug, "%s: alloc(%p,%lu,%lu) = %lu", MEMDBG_STR, ptr, n, size, n*size);
#endif
  return (ptr);
}
#endif

#ifdef _POSIX_MEMLOCK
void *
xmalloc_lock(size_t n, size_t size)
{
  void *ptr = xmalloc(n, size);
  if (mlock(ptr, n*size) != 0) {
    W2_errstr("could not lock memory ..");
  }
  return (ptr);
}
#endif

void *
xrealloc(void *ptr, size_t size, size_t new_size)
{
  void *new_ptr = NULL;
  int diff_size = (new_size - size);

  assert(ptr);
  if (diff_size == 0) return (ptr);
#ifdef ENABLE_PTHREADS
  pthread_mutex_lock(&thrd_realloc);
#endif
  if (diff_size > 0)
  {
    total_bytes_allocd += diff_size;
  } else if (diff_size < 0) {
    total_bytes_freed += diff_size;
  }
  new_ptr = (void *) realloc(ptr, size + diff_size);
  xmem_obj_chg(ptr, new_ptr, size, diff_size);
#ifdef ENABLE_PTHREADS
  pthread_mutex_unlock(&thrd_realloc);
#endif
  assert(new_ptr);
  return (new_ptr);
}

void
xfree(void *ptr, size_t size)
{
  if (!ptr) return;
  free(ptr);
#ifdef ENABLE_PTHREADS
  pthread_mutex_lock(&thrd_free);
#endif
  total_bytes_freed += size;
  total_blocks_freed++;
  xmem_obj_del(ptr, size);
#ifdef ENABLE_PTHREADS
  pthread_mutex_unlock(&thrd_free);
#endif
}

void
xmem_usage(void)
{
  struct mallinfo mi = mallinfo();
  I("[Memory Usage] %lu bytes (in %lu blocks) alloc'd, %lu bytes (in %lu blocks) free'd, %lu bytes (%lu blocks) in use", total_bytes_allocd, total_blocks_allocd, total_bytes_freed, total_blocks_freed, total_bytes_allocd-total_bytes_freed, total_blocks_allocd-total_blocks_freed);
  I("[Global Memory Usage] %d bytes avail, %d bytes used by alloc's, %d bytes in free blocks, %d bytes alloc'd by mmap", mi.arena, mi.uordblks, mi.fordblks, mi.hblkhd); 
}
